package owg.util.editor2d;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;

import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

import owg.util.InstanceList;
import owg.util.awt.TypeEditor;
import owg.util.awt.VerticalLayout;
import owg.util.data.Hidden;
import owg.util.data.KryoInstance;
import owg.util.editor2d.entity.SP;
import owg.util.euclidian.V2D;

public class ClassUI extends JPanel
{
	private static final long serialVersionUID = 6422531800015160704L;
	
	Editor2DUI editorUI;
	JTabbedPane constructorsPane;
	Class<?> currentClass;
	TypeEditor<?>[][] params;
	Constructor<?>[] constructors;
	int[] posIndices;
	int[] iListIndices;
	int[] startIndices;
	
	public ClassUI(Editor2DUI editorUI, Class<?> initialClass)
	{
		super(new BorderLayout());
		this.editorUI = editorUI;
		setClass(initialClass);	
	}
	
	/**Must be called before the constructors pane is nulled...*/
	public void dumpTabIndexToFile(Class<?> currentClass)
	{
		int tabIndex = getCurrentTabIndex();
		
		File f = new File(consDir(), currentClass.getCanonicalName()+"_tab.txt");
		FileWriter pw = null;
		try {
			pw = new FileWriter(f);
			pw.write(tabIndex);
		}
		catch (IOException e) {e.printStackTrace();}
		finally {try{if(pw != null) pw.close();}catch(Exception e){}}
	}
	
	public int getCurrentTabIndex()
	{
		return constructorsPane.getSelectedIndex();
	}
	
	public void dumpParamsToFiles(Class<?> currentClass)
	{
		if(params == null)
		{
			System.err.println("Warning: Attempting to dump null params");
			return;
		}
		
		for(int i= 0; i < params.length; i++)
		{
			File f = new File(consDir(), currentClass.getCanonicalName()+"_"+i+".dat");
			Output out = null;
			try {
				out = new Output(new FileOutputStream(f));
				Object[] paramsArray = new Object[params[i].length];
				for(int j = 0; j<params[i].length; j++)
					paramsArray[j] = params[i][j].getValue();
				KryoInstance.kryo.writeClassAndObject(out, paramsArray);
			}
			catch (IOException e) {e.printStackTrace();}
			finally {try{if(out != null) out.close();}catch(Exception e){}}
		}
	}
	
	public void setClass(Class<?> currentClass)
	{
		this.currentClass = currentClass;
		this.removeAll();
		
		JPanel constructorsPanel = new JPanel(new VerticalLayout(5, VerticalLayout.BOTH, VerticalLayout.TOP));
		constructorsPanel.setBorder(BorderFactory.createTitledBorder("Constructors"));
		
		Constructor<?>[] constructors = currentClass.getConstructors();
		startIndices = new int[constructors.length];//Possibly too long, but I can spare the bytes
		posIndices = new int[constructors.length];
		iListIndices = new int[constructors.length];
		ArrayList<Constructor<?>> consList = new ArrayList<>();
		
		constructorsPane = new JTabbedPane();
		ArrayList<TypeEditor<?>[]> editorsInAllTabs = new ArrayList<TypeEditor<?>[]>();
		int count = 0;
		for(int i = 0; i < constructors.length; i++)
		{
			final Constructor<?> cons = constructors[i];
			//Only use the public constructors
			if(Modifier.isPublic(cons.getModifiers()) && cons.getAnnotation(Hidden.class) == null)//Only get public, non-hidden
			{
				JPanel cPanel = new JPanel(new GridBagLayout());
				GridBagConstraints c = new GridBagConstraints();
				c.gridx = 0;
				c.gridy = 0;
				c.fill = GridBagConstraints.HORIZONTAL;
				c.anchor = GridBagConstraints.NORTH;
				final Parameter[] parameterList = cons.getParameters();
				
				//Assume that the first parameter is the position, but don't frak up if it isn't
				int start;
				int posIndex = -1;
				int iListIndex = -1;
				for(start = 0; start < parameterList.length; start++)
				{
					if(parameterList[start].getType().equals(V2D.class) && posIndex == -1)
						posIndex = start;
					else if(InstanceList.class.isAssignableFrom(parameterList[start].getType()) && iListIndex == -1)
						iListIndex = start;
					else
						break;//We have all default params or the non-default params start here 
				}
				
				final TypeEditor<?>[] editorsInThisTab = new TypeEditor<?>[parameterList.length - start];
				editorsInAllTabs.add(editorsInThisTab);
				
				c.gridwidth = 1;
				Object[] paramValues = getDefaultParamValues(currentClass, i, parameterList.length-start);
				
				for(int j = start; j< parameterList.length; j++)
				{
					c.gridx = 0;
					c.weightx = 0;
					
					//Find name and store all SP modifiers.
					String pName = parameterList[j].toString();
					SP modifiers = new SP(parameterList[j].getAnnotations());
					
					cPanel.add(new JLabel(pName), c);
					
					c.gridx = 1;
					c.weightx = 1;
					
					final TypeEditor<?> ed = makeEditor(parameterList[j].getType(), paramValues[j-start], modifiers);
					editorsInThisTab[j-start] = ed;
					
					if(ed != null)
						cPanel.add(ed, c);
					
					c.gridy++;
				}
				
				c.gridwidth = 2;
				c.weightx = 1;
				c.gridx = 0;
				
				c.weighty = 1;
				Dimension d = new Dimension(1,1);
				cPanel.add(new Box.Filler(d, d, d),c);
				startIndices[i] = start;
				posIndices[i] = posIndex;
				iListIndices[i] = iListIndex;
				consList.add(cons);
				
				constructorsPane.addTab(String.valueOf(count+1), cPanel);
				count++;
			}
		}
		this.constructors = new Constructor[consList.size()];
		consList.toArray(this.constructors);
		params = new TypeEditor<?>[editorsInAllTabs.size()][];
		editorsInAllTabs.toArray(params);
		editorsInAllTabs = null;
		constructorsPane.setSelectedIndex(getDefaultTabIndex(currentClass, count));
		
		constructorsPanel.add(constructorsPane);
		add(constructorsPanel);
		
		revalidate();
		repaint();
	}
	
	private<X> TypeEditor<X> makeEditor(Class<X> type, Object val, SP modifiers) {
		X value;
		try
		{
			if(type.isPrimitive())
				type = Boxed.getBoxedType(type);
			value = type.cast(val);
		}
		catch(ClassCastException e)
		{
			e.printStackTrace();
			value = null;
		}
		return TypeEditorFactory.getEditor(type, value, modifiers);
	}
	
	private File consDir()
	{
		File consDir = new File(editorUI.editor.workingDir, "editorSettings/cons");
		if(!consDir.exists())
		{
			if(!consDir.mkdirs())
				System.err.println("Error: Constructor settings directory "+consDir+" does not exist and could not be created!");
		}
		else if(!consDir.isDirectory())
			System.err.println("Error: Constructor settings directory "+consDir+" exists but is not a directory!");
		return consDir;
	}
	
	private int getDefaultTabIndex(Class<?> currentClass, int max)
	{
		File f = new File(consDir(), currentClass.getCanonicalName()+"_tab.txt");
		System.out.print("Reading: "+f+"... ");
		
		FileReader br = null;
		try {
			if(!f.exists())
			{
				throw new FileNotFoundException("Could not locate default tab file: "+f);
			}
			br = new FileReader(f);
			int tabIndex = br.read();
			if(tabIndex >= max)
				tabIndex = max-1;
			System.out.println("Success.");
			return tabIndex;
		}
		catch (Exception e) 
		{
			if(e instanceof FileNotFoundException)
				System.out.println("File not found, using tab 0");
			else
			{
				System.err.println(e.getMessage()+", using tab 0");
				e.printStackTrace();
			}
			return 0;
		}
		finally {try{if(br!=null) br.close();}catch(Exception e){}}
	}
	
	private Object[] getDefaultParamValues(Class<?> currentClass, int i, int expectedLength)
	{
		Input input = null;
		try
		{
			File f = new File(consDir(), currentClass.getCanonicalName()+"_"+i+".dat");	
			System.out.print("Reading: "+f+"... ");
			if(!f.exists())
				throw new FileNotFoundException("Could not locate constructor parameter file: "+f);
			input = new Input(new FileInputStream(f));
			
			Object[] result = (Object[])KryoInstance.kryo.readClassAndObject(input);
			if(result.length != expectedLength)
				throw new IllegalArgumentException("Length does not match - Found: "+result.length+", expected: "+expectedLength);
			System.out.println("Success.");
			return result;
		}
		catch (Exception e) 
		{
			if(e instanceof IllegalArgumentException)
				System.out.println(e.getMessage()+", using defaults");
			else if(e instanceof FileNotFoundException)
				System.out.println("File not found, using defaults");
			else
			{
				System.err.println(e.getMessage()+", using defaults");
				e.printStackTrace();
			}
			return new Object[expectedLength];
		}
		finally {try{if(input!=null) input.close();}catch(Exception e){}}
	}
	
	public ConstructorParams getConstructorParams()
	{
		int index = getCurrentTabIndex();
		int numEditableParams = params[index].length;
		int totalNumParams = numEditableParams + startIndices[index];
		
		Object[] values = new Object[totalNumParams];
		for(int i = 0; i< numEditableParams; i++)
			values[startIndices[index]+i] = params[index][i].getValue();
		return new ConstructorParams(constructors[index], values, posIndices[index], iListIndices[index]);
	}
	
}
